package com.daikun.common.aop.handler;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.daikun.common.aop.annos.BaseLock;
import com.daikun.common.enums.BaseResultEnum;
import com.daikun.common.exception.BizException;
import com.daikun.common.utils.BaseLockUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;

/**
 * 锁注解 {@link BaseLock} 解析器
 * @author earl created 2025-07-10
 * @version init 0.0.1
 */
@Slf4j
@Aspect
@Component
public class BaseLockHandler {

    @Pointcut("@annotation(com.daikun.common.aop.annos.BaseLock)")
    public void invoke() {}

    @Around("invoke()")
    public Object around(ProceedingJoinPoint point) {
        try {
            MethodSignature signature = (MethodSignature) point.getSignature();
            BaseLock baseLock = AnnotationUtil.getAnnotation(signature.getMethod(), BaseLock.class);

            String s = null;
            Object[] args = point.getArgs();
            for (Object arg : args) {
                if (arg != null && JSONUtil.isTypeJSON(arg.toString())) {
                    Object o = JSONUtil.parseObj(arg).get(baseLock.keyName());
                    if (o != null && StrUtil.isNotBlank(o.toString())) {
                        s = o.toString();
                        break;
                    }
                }
            }
            if (StrUtil.isNotBlank(s)) {
                return lock(point, baseLock, s);
            }
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (requestAttributes == null) {
                return point.proceed();
            }
            HttpServletRequest request = requestAttributes.getRequest();
            s = getParamData(request, baseLock.keyName());
            if (StrUtil.isBlank(s)) {
                s = getPathVariableData(request, baseLock.keyName());
            }
            if (StrUtil.isBlank(s)) {
                s = getHeaderData(request, baseLock.keyName());
            }
            if (StrUtil.isNotBlank(s)) {
                return lock(point, baseLock, s);
            }
            throw new BizException(BaseResultEnum.PARAM_ERROR, "参数缺失 或者 BaseLock 注解中的变量名称错误");
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对拦截的方法进行加锁
     * @param point 切点
     * @param baseLock 注解
     * @param s 参数中读取的 key
     * @return 拦截的方法返回值
     */
    private static Object lock(ProceedingJoinPoint point, BaseLock baseLock, String s) {
        String lockKey = baseLock.value() + StrUtil.C_COLON + s;
        try {
            boolean b = BaseLockUtil.tryLock(lockKey, baseLock.unit(), baseLock.waitTime(), baseLock.holdTime());
            if (b) {
                return point.proceed();
            } else {
                throw new BizException(BaseResultEnum.LOCK_FAIL, "加锁失败");
            }
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            BaseLockUtil.unLock(lockKey);
        }
    }

    /**
     * 从请求 GET 请求的路径 或者 form 表单中获取参数
     * @param keyName 要获取的数据的 key
     * @return 获取到的数据，返回值可能为 null
     */
    private static String getParamData(HttpServletRequest request, String keyName) {
        String s = request.getParameter(keyName);
        return StrUtil.isBlank(s) ? null : s;
    }

    /**
     * 从请求路径获取参数
     * @param keyName 要获取的数据的 key
     * @return 获取到的数据，返回值可能为 null
     */
    private static String getPathVariableData(HttpServletRequest request, String keyName) {
        Object o = JSONUtil.parseObj(request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).get(keyName);
        return o == null ? null : (StrUtil.isBlank(o.toString()) ? null : o.toString());
    }

    /**
     * 从请求头获取参数
     * @param keyName 要获取的数据的 key
     * @return 获取到的数据，返回值可能为 null
     */
    private static String getHeaderData(HttpServletRequest request, String keyName) {
        String s = request.getHeader(keyName);
        return StrUtil.isBlank(s) ? null : s;
    }
}
